/*  This file is part of -_-.

    -_- is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    -_- is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with -_-.  If not, see <http://www.gnu.org/licenses/>.

    Copyright 2012-2013 Oliver Katz */

#include "mod_codeserver.h"
#include "../../common/debug.h"

extern "C"
{
    #include "../../common/data.h"
    #include "../../common/fio.h"
    #include "../../bus/ipc.h"
    #include "../../opal/std/subprocess.h"
    #include "../../opal/std/io.h"
    #include "../../common/types/vector.h"
    #include "../../common/types/string.h"
    #include "../../common/types/assoc.h"
    #include "../../common/types/pair.h"
}

#include "../arch_c_compat.h"

namespace sleepyface
{
    namespace arch
    {
        namespace modules
        {
            CodeServer::CodeServer() : ArchModule()
            {
                setName("code-server");
            }

            int CodeServer::linkExternal(string proto, void *func)
            {
                virtualMachine.linkExternal(server.compilePrototype(server.parseAST(proto, "-preamble-"), "-preamble-"), func);
                if (opal::dumpErrors())
                {
                    Z_WARNING("error linking external: " << proto);
                    return 1;
                }
                else
                    return 0;
            }

            int CodeServer::macroExternal(string n, string b)
            {
                opal::AST na = server.parseAST(n, "-preamble-");
                opal::AST ba = server.parseAST(b, "-preamble-");

                if (na.isLeaf())
                {
                    server.macro(na.get().get(), ba);
                }
                else
                {
                    vector<string> tmp;
					for (int i = 0; i < na.getBranches().size(); i++)
                    {
                        if (!na.getBranches()[i].isLeaf())
                        {
                            Z_WARNING("macro arguments don't have arguments of their own - they would progressively get smaller and smaller like Russian dolls or something");
                            return 1;
                        }
                        tmp.push_back(na.getBranches()[i].get().get());
                    }
                    server.macro(na.get().get(), tmp, ba);
                }

                return 0;
            }

#define MACRO(m) macroExternal(#m, #m)

            void CodeServer::linkInternalLibraries()
            {
                // common/data.h
                MACRO(Z_TYPEID_NULL);
                MACRO(Z_TYPEID_INT);
                MACRO(Z_TYPEID_STR);
                MACRO(Z_TYPEID_VECTOR);
                MACRO(Z_TYPEID_ASSOC);
                MACRO(Z_TYPEID_PAIR);
                linkExternal("decl xi8 z_data_new (int t) (int s)", (void *)z_data_new);
                linkExternal("decl int z_data_delete (xi8 d)", (void *)z_data_delete);
                linkExternal("decl int z_data_typeid_get (xi8 d)", (void *)z_data_typeid_get);
                linkExternal("decl int z_data_typeid_set (xi8 d) (int t)", (void *)z_data_typeid_set);
                linkExternal("decl int z_data_size_get (xi8 d)", (void *)z_data_size_get);
                linkExternal("decl int z_data_size_set (xi8 d) (int s)", (void *)z_data_size_set);
                linkExternal("decl xi8 z_data_src_get (xi8 d)", (void *)z_data_src_get);
                linkExternal("decl xi8 z_data_src_set (xi8 d) (xi8 s)", (void *)z_data_src_set);
                linkExternal("decl int z_data_retrieve_query_code (xi8 d)", (void *)z_data_retrieve_query_code);
                linkExternal("decl int z_data_assign_query_code (xi8 d) (int c)", (void *)z_data_assign_query_code);
                linkExternal("decl int z_data_eq (xi8 a) (xi8 b)", (void *)z_data_eq);
                linkExternal("decl int z_data_null (xi8 d)", (void *)z_data_null);

                // common/fio.h
                MACRO(Z_FIO_WHOLE);
                MACRO(Z_FIO_BUFSIZE_DEFAULT);
                linkExternal("decl xi8 z_freader_new (xi8 path) (int bufsize)", (void *)z_freader_new);
                linkExternal("decl int z_freader_delete (xi8 f)", (void *)z_freader_delete);
                linkExternal("decl int z_freader_ok (xi8 f)", (void *)z_freader_ok);
                linkExternal("decl int z_freader_read (xi8 f)", (void *)_z_freader_read);
                linkExternal("decl int z_freader_left (xi8 f)", (void *)z_freader_left);

                // bus/ipc.h
                linkExternal("decl xi8 z_ipc_server_new_global (int port)", (void *)z_ipc_server_new_global);
                linkExternal("decl xi8 z_ipc_server_new_local (int port)", (void *)z_ipc_server_new_local);
                linkExternal("decl int z_ipc_server_delete (xi8 i)", (void *)z_ipc_server_delete);
                linkExternal("decl int z_ipc_server_connect (xi8 i)", (void *)z_ipc_server_connect);
                linkExternal("decl int z_ipc_server_disconnect (xi8 i)", (void *)z_ipc_server_disconnect);
                linkExternal("decl xi8 z_ipc_server_wait (xi8 i)", (void *)_z_ipc_server_wait);
                linkExternal("decl int z_ipc_server_reply (xi8 i) (xi8 d)", (void *)z_ipc_server_reply);
                linkExternal("decl int z_ipc_server_reply_empty (xi8 i)", (void *)z_ipc_server_reply_empty);
                linkExternal("decl xi8 z_ipc_client_new_global (xi8 addr) (int port)", (void *)z_ipc_client_new_global);
                linkExternal("decl xi8 z_ipc_client_new_local (xi8 addr) (int port)", (void *)z_ipc_client_new_local);
                linkExternal("decl int z_ipc_client_delete (xi8 i)", (void *)z_ipc_client_delete);
                linkExternal("decl int z_ipc_client_connect (xi8 i)", (void *)z_ipc_client_connect);
                linkExternal("decl int z_ipc_client_disconnect (xi8 i)", (void *)z_ipc_client_disconnect);
                linkExternal("decl int z_ipc_client_send_request (xi8 i) (xi8 d)", (void *)z_ipc_client_send_request);
                linkExternal("decl xi8 z_ipc_client_recieve_response (xi8 i)", (void *)_z_ipc_client_recieve_response);

                // arch/arch_c_compat.h
                linkExternal("decl int arch.getType (xi8 v)", (void *)z_value_get_type);
                linkExternal("decl int arch.getBool (xi8 v)", (void *)z_value_get_bool);
                linkExternal("decl int arch.getInt (xi8 v)", (void *)z_value_get_int);
                linkExternal("decl xi8 arch.getString (xi8 v)", (void *)z_value_get_string);
                linkExternal("decl xi8 arch.getData (xi8 v)", (void *)z_value_get_data);
                linkExternal("decl xi8 arch.newInt (int v)", (void *)z_value_new_int);
                linkExternal("decl xi8 arch.newString (xi8 v)", (void *)z_value_new_string);
                linkExternal("decl xi8 arch.newData (xi8 v)", (void *)z_value_new_data);
                linkExternal("decl int arch.signal0 (xi8 m) (int s)", (void *)z_arch_signal0);
                linkExternal("decl int arch.signal1 (xi8 m) (int s) (xi8 a0)", (void *)z_arch_signal1);
                linkExternal("decl int arch.signal2 (xi8 m) (int s) (xi8 a0) (xi8 a1)", (void *)z_arch_signal2);
                linkExternal("decl xi8 arch.get (xi8 m) (xi8 k0) (xi8 k1)", (void *)z_arch_get);
                linkExternal("decl xi8 arch.set (xi8 m) (xi8 k0) (xi8 k1) (xi8 v)", (void *)z_arch_set);
                linkExternal("decl xi8 arch.list (xi8 m)", (void *)z_arch_keylist);
                linkExternal("decl int arch.isLoaded (xi8 m)", (void *)z_arch_is_loaded);
                linkExternal("decl xi8 arch.modules", (void *)z_arch_list);
                linkExternal("decl int arch.memusage", (void *)z_arch_get_memory_usage);

                macroExternal("arch.signalLimit", "3");
                macroExternal("arch.signalUpdate", "(+ 1 arch.signalLimit)");
                macroExternal("arch.signalConfiguration", "(+ 1 arch.signalLimit)");
                macroExternal("arch.signalCompileFile", "(+ 2 arch.signalLimit)");
                macroExternal("arch.signalCompileLine", "(+ 3 arch.signalLimit)");
                macroExternal("arch.signalUnJIT", "(+ 4 arch.signalLimit)");
                macroExternal("arch.signalShell", "(+ 5 arch.signalLimit)");
                macroExternal("arch.signalShellBackground", "(+ 6 arch.signalLimit)");
                macroExternal("arch.signalLinkExternal", "(+ 7 arch.signalLimit)");
                macroExternal("arch.signalLog", "(+ 1 arch.signalLimit)");
                macroExternal("arch.signalConnect", "(+ 1 arch.signalLimit)");
                macroExternal("arch.signalDisconnect", "(+ 2 arch.signalLimit)");
                macroExternal("arch.signalShareVariableRW", "(+ 1 arch.signalLimit)");
                macroExternal("arch.signalShareVariableRO", "(+ 2 arch.signalLimit)");
                macroExternal("arch.signalRunForeground", "(+ 3 arch.signalLimit)");
                macroExternal("arch.signalRunBackground", "(+ 4 arch.signalLimit)");
            }

            void CodeServer::linkStandardLibrary()
            {
                // opal/std/subprocess.h
                linkExternal("decl int !.spawn (xi32 args)", (void *)z_subprocess_spawn_sync);
                linkExternal("decl int !.spawnForked (xi32 args)", (void *)z_subprocess_spawn_async);
                linkExternal("decl int !.spawnRedirect (xi32 args)", (void *)z_subprocess_spawn_async_redirect);
                linkExternal("decl int !.wait (int pid)", (void *)z_subprocess_wait);

                // opal/std/io.h
                linkExternal("decl xi8 io.open (xi8 path) (xi8 perm)", (void *)fopen);
                linkExternal("decl int io.close (xi8 handle)", (void *)fclose);
                linkExternal("decl int io.write (xi8 handle) (xi8 str)", (void *)z_io_write);
                linkExternal("decl int io.append (xi8 handle) (xi8 str)", (void *)z_io_append);
                macroExternal("io.reader.new path bufsize", "z_freader_new path bufsize");
                macroExternal("io.reader.delete reader", "z_freader_delete reader");
                macroExternal("io.reader.ok reader", "z_freader_ok reader");
                macroExternal("io.reader.read reader", "z_freader_read reader");
                macroExternal("io.reader.left reader", "z_freader_left reader");

                // common/data.h
                macroExternal("data.null", "0");
                macroExternal("data.int", "1");
                macroExternal("data.str", "2");
                macroExternal("data.vector", "3");
                macroExternal("data.assoc", "4");
                macroExternal("data.pair", "5");
                linkExternal("decl xi8 data.new (int t) (int s)", (void *)z_data_new);
                linkExternal("decl int data.delete (xi8 d)", (void *)z_data_delete);
                linkExternal("decl int data.typeGet (xi8 d)", (void *)z_data_typeid_get);
                linkExternal("decl int data.typeSet (xi8 d) (int t)", (void *)z_data_typeid_set);
                linkExternal("decl int data.sizeGet (xi8 d)", (void *)z_data_size_get);
                linkExternal("decl int data.sizeSet (xi8 d) (int s)", (void *)z_data_size_set);
                linkExternal("decl xi8 data.srcGet (xi8 d)", (void *)z_data_src_get);
                linkExternal("decl xi8 data.srcSet (xi8 d) (xi8 s)", (void *)z_data_src_set);
                linkExternal("decl int data.retrieveQueryCode (xi8 d)", (void *)z_data_retrieve_query_code);
                linkExternal("decl int data.assignQueryCode (xi8 d) (int c)", (void *)z_data_assign_query_code);
                linkExternal("decl int data.eq (xi8 a) (xi8 b)", (void *)z_data_eq);
                linkExternal("decl int data.null (xi8 d)", (void *)z_data_null);

                // common/types/vector.h
                linkExternal("decl xi8 vector.new", (void *)_z_vector_new);
                linkExternal("decl int vector.delete (xi8 v)", (void *)z_vector_del);
                linkExternal("decl int vector.push (xi8 v) (xi8 d)", (void *)_z_vector_push);
                linkExternal("decl xi8 vector.peek (xi8 v)", (void *)_z_vector_peek);
                linkExternal("decl xi8 vector.pop (xi8 v)", (void *)_z_vector_pop);
                linkExternal("decl xi8 vector.at (xi8 v) (int idx)", (void *)z_vector_at);
                linkExternal("decl int vector.size (xi8 v)", (void *)z_vector_size);
                linkExternal("decl int vector.erase (xi8 v) (int idx)", (void *)z_vector_erase);
                linkExternal("decl int vector.insert (xi8 vdst) (xi8 vsrc) (int idx)", (void *)z_vector_insert);
                linkExternal("decl xi32 vector.toArray (xi8 v)", (void *)z_vector_to_array);
                linkExternal("decl xi8 vector.fromArray (xi32 a)", (void *)z_array_to_vector);

                // common/types/string.h
                linkExternal("decl xi8 string.new", (void *)_z_string_new);
                linkExternal("decl xi8 string.newFrom (xi8 s)", (void *)_z_string_new_from);
                linkExternal("decl int string.delete (xi8 s)", (void *)z_string_del);
                linkExternal("decl char string.at (xi8 s) (int idx)", (void *)z_string_at);
                linkExternal("decl int string.size (xi8 s)", (void *)z_string_size);
                linkExternal("decl int string.find (xi8 s) (xi8 p)", (void *)z_string_find_string);
                linkExternal("decl int string.findChar (xi8 s) (char c)", (void *)z_string_find_char);
                linkExternal("decl xi8 string.substring (xi8 s) (int idx0) (int idx1)", (void *)_z_string_substring);
                linkExternal("decl xi8 string.safe (xi8 s)", (void *)z_string_safe);

                // common/types/assoc.h
                linkExternal("decl xi8 assoc.new", (void *)_z_assoc_new);
                linkExternal("decl int assoc.delete (xi8 a)", (void *)z_assoc_del);
                linkExternal("decl xi8 assoc.at (xi8 a) (xi8 s)", (void *)z_assoc_at);

                // common/types/pair.h
                linkExternal("decl xi8 pair.new (xi8 f) (xi8 s)", (void *)_z_pair_new);
                linkExternal("decl int pair.delete (xi8 p)", (void *)z_pair_del);
                linkExternal("decl xi8 pair.first (xi8 p)", (void *)z_pair_first);
                linkExternal("decl xi8 pair.second (xi8 p)", (void *)z_pair_second);

                // assertion
                macroExternal("assert.unjit name", "arch.signal1 \"code-server\" arch.signalUnJIT (arch.newString name)");
                macroExternal("assert.insertFromString name line", "arch.signal2 \"code-server\" arch.signalCompileLine (arch.newString name) (arch.newString line)");
                macroExternal("assert.pullAST name", "arch.get \"code-server\" name \"ast\"");
                macroExternal("assert name ast", "arch.set \"code-server\" name \"ast\" (arch.newData ast)");
            }

#undef MACRO

            opal::AST CodeServer::dynamicTreeToAST(z_data *t)
            {
                if (z_data_typeid_get(t) == Z_TYPEID_STR)
                {
                    return opal::AST(opal::Token(string(z_string_safe(t)), 0, 0));
                }
                else if (z_data_typeid_get(t) == Z_TYPEID_VECTOR)
                {
                    if (z_vector_size(t) == 0)
                        return opal::AST();
                    else
                    {
                        opal::AST rtn = opal::AST(opal::Token(string(z_string_safe(z_vector_at(t, 0))), 0, 0));
                        for (int i = 1; i < z_vector_size(t); i++)
                            rtn.branch(opal::AST(opal::Token(string(z_string_safe(z_vector_at(t, i))), 0, 0)));
                        return rtn;
                    }
                }
                else
                {
                    Z_WARNING("data of type " << z_data_typeid_get(t) << " is not allowed in dynamic AST trees");
                    return opal::AST();
                }
            }

            z_data *CodeServer::ASTToDynamicTree(opal::AST a)
            {
                if (a.get().get().empty())
                {
                    Z_WARNING("cannot create a dynamic tree from an empty AST");
                    return NULL;
                }

                if (a.isLeaf())
                {
                    return _z_string_new_from((char *)a.get().get().c_str());
                }
                else
                {
                    z_data *rtn = _z_vector_new();
                    z_vector_push(rtn, z_string_new_from((char *)a.get().get().c_str()));
                    for (vector<opal::AST>::iterator i = a.getBranches().begin(); i != a.getBranches().end(); i++)
                    {
                        _z_vector_push(rtn, ASTToDynamicTree(*i));
                    }
                    return rtn;
                }
            }

            int CodeServer::signal(ModuleSignal s, Architecture &a)
            {
                if (s.getCode() == signalInitialize)
                {
                    virtualMachine.setOptimizationLevel(1);
                    linkInternalLibraries();
                    linkStandardLibrary();
                    return 0;
                }
                else if (s.getCode() == signalCommandLineArguments)
                {
                    bool e = 0;
                    for (int i = 0; i < a.getArgc(); i++)
                    {
                        if (string(a.getArgv()[i]).find("--opal-cfg:") != string::npos)
                        {
                            string opt = string(a.getArgv()[i]).substr(string("--opal-cfg:").size());
                            if (opt.find("=") == string::npos)
                            {
                                Z_WARNING("opal configuration options must be of the form --opal-cfg:<KEY>=<VALUE>");
                                return 1;
                            }
                            string k = opt.substr(0, opt.find("="));
                            string v = opt.substr(opt.find("=")+1);
                            server.cfg(k, v);
                            cout << "\"" << k << "\" <= \"" << v << "\"\n";
                            e++;
                        }
                    }

                    if (e > 0)
                        cout << "\n";
                    return 0;
                }
                else if (s.getCode() == signalConfiguration)
                {
                    if (s.size() != 2)
                        return 1;
                    server.cfg(s.getData(0).getString(), s.getData(1).getString());
                    return 0;
                }
                else if (s.getCode() == signalCompileFile)
                {
                    if (s.size() != 1)
                        return 1;
                    void *f = server.compile(s.getData(0).getString(), virtualMachine);
                    if (f == NULL)
                        return -1;
                    vm::LiteralValue v = server.executeFunction(server.cfg("main-symbol"), f);
                    return v.castInt().get();
                }
                else if (s.getCode() == signalCompileLine)
                {
                    if (s.size() != 2)
                        return 1;
                    opal::AST ast = server.parseAST(s.getData(1).getString(), "--");
                    vm::FunctionalNode fn = server.compileFunctionalNode(ast, "--");
                    vm::Body b = server.createWrapperFunction(s.getData(0).getString(), fn);
                    void *f = virtualMachine.JIT(b, server.getDebugLLVMModule());
                    if (f == NULL)
                    {
                        opal::error(ast.get(), "--", server.cfg("error-failed-compilation-jit"));
                        opal::dumpErrors();
                        return -1;
                    }
                    else
                    {
                        vm::LiteralValue v = server.executeFunction(s.getData(0).getString(), f);
                        return v.castInt().get();
                    }
                }
                else if (s.getCode() == signalUnJIT)
                {
                    return virtualMachine.unJIT(s.getData(0).getString());
                }
                else if (s.getCode() == signalShell)
                {
                    server.shell(virtualMachine);
                    return 0;
                }
                else if (s.getCode() == signalShellBackground)
                {
                    server.shellBackground(virtualMachine);
                    return 0;
                }
                else if (s.getCode() == signalLinkExternal)
                {
                    if (linkExternal(s.getData(0).getString(), s.getData(1).getData()))
                        return 1;
                }
                else
                {
                    return 0;
                }
            }

            ModuleValue CodeServer::get(string k0, string k1, Architecture &a)
            {
                if (k1.compare("ast") == 0)
                {
                    return ModuleValue(ModuleValue::_data, (void *)ASTToDynamicTree(server.getFunctionAST(k0)));
                }
                else
                {
                    return ModuleValue();
                }
            }

            ModuleValue CodeServer::set(string k0, string k1, ModuleValue v, Architecture &a)
            {
                if (k1.compare("ast") == 0)
                {
                    opal::AST ast = dynamicTreeToAST((z_data *)v.getData());
                    server.compileMain(ast, "-preamble-", virtualMachine);
                    return ModuleValue();
                }
                else
                {
                    return ModuleValue();
                }
            }

            vector<string> CodeServer::list(Architecture &a)
            {
                return virtualMachine.listJITedFunctions();
            }
        }
    }
}
